explorer: heatmap mutual-exclusivity (phase 3) + self-refresh loop fix (#233)#242
Merged
Merged
Conversation
phase 3) Heatmap was an orthogonal overlay painting on top of the cluster/point dots, producing the dots-vs-hotspots disagreement RY flagged 2026-05-27: two layers telling contradictory spatial stories at once. Phase 3 makes heatmap stand alone. Add a single applyLayerVisibility() helper — now the ONLY writer of `.show` on h3Points/samplePoints — that hides both marker collections while the heatmap is on and restores the altitude-driven mode's collection when it's off. enterPointMode/ exitPointMode and the heatmap toggle handler all route through it. Also hide the #facetNote apology while the heatmap is on: the note ("filters apply at sample zoom level") is false when the heatmap shows filtered density directly. syncFacetNote() gains a heatmap conjunct. Tests: 2 new specs in heatmap-overlay.spec.js (markers hide/restore on toggle; facetNote hidden when heatmap on). Full heatmap suite green (8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lesorg#233) RY observed the heatmap re-rendering repeatedly with no user input. Cause: the overlay refreshes on Cesium `moveEnd`, keyed off `computeViewRectangle`. With Cesium World Terrain enabled, the camera height keeps settling for a beat after a move (terrain-collision adjustment as tiles stream in) and inertia drifts post mouse-up — each tiny settle fired `moveEnd`, the old exact-key dedupe (`toFixed(4)`, ~11 m) let the jitter through, the re-render nudged terrain again, and the loop sustained. High-altitude views never looped (coarse, stable terrain) — consistent with the symptom being intermittent and zoom/region dependent. Fix: refreshHeatmap() now dedupes on a 2%-of-span tolerance against the last SUCCESSFULLY-rendered view + filter (heatmapLastBounds / heatmapLastFilterHash), instead of exact-key equality. Sub-meaningful jitter stays under the threshold and is ignored; real pans/zooms exceed it and render. The snapshot is deliberately NOT cleared on moveStart, so jitter that fires full moveStart/moveEnd cycles is still measured against the committed overlay and can't re-arm the loop — and it can't wedge, because any meaningful move always exceeds tolerance. Details (Codex 2-round review): - lngDelta() folds the longitude delta to [0,180] so antimeridian-adjacent views (wrapped cLng≈180 vs settled cLng≈-177) aren't read as a ~357° move. - skip-branch restores a truthful "rendered from N samples" status (gated on heatmapLastBounds, so zero-sample overlays restore too) instead of leaving a moveStart "waiting for camera" stuck. - viewer._heatmapSkips counts tolerance skips so the regression test can PROVE the skip branch ran (not a vacuous "unchanged timestamp" assertion). Test: new regression spec — a sub-threshold nudge increments the skip counter, moves the camera, yet does NOT re-render (overlay intact); a ~6° move does re-render. Full heatmap suite green (10). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rdhyee
added a commit
to rdhyee/isamplesorg.github.io
that referenced
this pull request
May 29, 2026
…samplesorg#243) Additive 'collection' dimension: filter the explorer to a named SamplingSite label (e.g. OpenContext 'PKAP Survey Area'). Precomputes site membership via the wide-parquet Sample->Event->Site traversal into two new R2 files; touches none of the existing facet files. Rebased onto main so it sits cleanly on top of the merged isamplesorg#242 heatmap work (disjoint regions, no conflict). - scripts/build_collections.py: builds collections.parquet + sample_collections .parquet. Unnests BOTH relationship arrays (multi-event/multi-site safe), counts DISTINCT pids, orders membership by collection_id for row-group pruning. PKAP=15,446 verified; both files live on data.isamples.org. - explorer.qmd: dual-UX collection facet (top-N checkboxes + search-the-tail), ?collection= URL param wired through the existing facet lifecycle and the facetFilterSQL() chokepoint (2nd subquery against sample_collections.parquet). - collections.qmd: Featured Collections page uses identity-based &collection=. - EXPLORER_STATE.md, data.qmd: document the new param and files. - tests/test_collections.py: page + facet-DOM checks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced May 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch carries two related #233 heatmap improvements:
1. Heatmap mutually exclusive with marker layers (phase 3)
Previously the heatmap painted an imagery layer on top of the cluster/point dots, so two layers told contradictory spatial stories at once — the "dots-vs-hotspots disagreement." Now the heatmap stands alone: turning it on hides the markers; off restores whichever collection the altitude-driven mode wants.
applyLayerVisibility()— single source of truth for.showonh3Points/samplePoints(aside from the one-time initializer).heatmap on ⇒ both hidden;off ⇒ cluster→h3Points / point→samplePoints.enterPointMode/exitPointModeand the heatmap toggle route through it.syncFacetNote()gains a heatmap conjunct: the "filters apply at sample zoom level" apology is false when the heatmap shows filtered density directly, so it's hidden while heatmap is on. Boot?heatmap=1inherits all of this.2. Stop the heatmap self-refresh loop (tolerance dedupe)
RY observed the heatmap re-rendering repeatedly with no user input. Cause: the overlay refreshes on Cesium
moveEnd, keyed offcomputeViewRectangle. With Cesium World Terrain enabled, the camera height keeps settling after a move (terrain-collision as tiles stream in) + inertial drift; the old exact-key dedupe (toFixed(4), ~11 m) let that jitter through → re-render → terrain nudge →moveEnd→ loop. High-altitude views never looped (coarse/stable terrain) — matching the intermittent symptom.Fix:
refreshHeatmap()dedupes on a 2%-of-span tolerance against the last successfully-rendered view + filter, instead of exact-key equality. Sub-meaningful jitter is ignored; real pans/zooms render. The snapshot is deliberately not cleared onmoveStart, so jitter firing fullmoveStart/moveEndcycles is still measured against the committed overlay and can't re-arm the loop — and can't wedge, since any meaningful move exceeds tolerance.lngDelta()folds the longitude delta to[0,180]so antimeridian-adjacent views aren't misread as a ~357° move.viewer._heatmapSkipscounts tolerance skips so the regression test proves the skip path ran.Tests
tests/playwright/heatmap-overlay.spec.js— 10/10 green locally:#facetNotehidden when heatmap on;heatmap=1boot hides markers.Provenance
Claude (plan + implementation), Codex 2-round review on each change (all findings resolved). Verified visually + Playwright; loop fix confirmed live by RY on local build.
🤖 Generated with Claude Code